use {
    crate::{
        identifier::Identifier,
        schema::{self, relativize_namespace},
    },
    std::{
        collections::BTreeMap,
        fmt::{self, Write},
        path::PathBuf,
    },
};

// The string to be used for each indentation level.
const INDENTATION: &str = "    ";

// The generated types will derive these traits.
const TRAITS_TO_DERIVE: &[&str] = &["Clone", "Debug"];

// This list of Rust keywords comes from https://doc.rust-lang.org/reference/keywords.html.
const RUST_KEYWORDS: &[&str] = &[
    "Self",
    "abstract",
    "as",
    "async",
    "await",
    "become",
    "box",
    "break",
    "const",
    "continue",
    "crate",
    "do",
    "dyn",
    "else",
    "enum",
    "extern",
    "false",
    "final",
    "fn",
    "for",
    "if",
    "impl",
    "in",
    "let",
    "loop",
    "macro",
    "macro_rules",
    "match",
    "mod",
    "move",
    "mut",
    "override",
    "priv",
    "pub",
    "ref",
    "return",
    "self",
    "static",
    "struct",
    "super",
    "trait",
    "true",
    "try",
    "type",
    "typeof",
    "union",
    "unsafe",
    "unsized",
    "use",
    "virtual",
    "where",
    "while",
    "yield",
];

// This struct represents a tree of schemas organized in a module hierarchy.
#[derive(Clone, Debug)]
struct Module {
    children: BTreeMap<Identifier, Module>,
    schema: schema::Schema,
}

// This enum represents a case convention for the `write_identifier` function below.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CaseConvention {
    Pascal,
    Snake,
}

use CaseConvention::{Pascal, Snake};

// This enum is used to distinguish between the ingress and egress versions of a type.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Direction {
    Atlas,
    In,
    Out,
}

use Direction::{Atlas, In, Out};

// Generate Rust code from a schema and its transitive dependencies.
#[allow(clippy::too_many_lines)]
pub fn generate(
    typical_version: &str,
    schemas: &BTreeMap<schema::Namespace, (schema::Schema, PathBuf, String)>,
) -> String {
    // Construct a tree of modules and schemas. We start with an empty tree.
    let mut tree = Module {
        children: BTreeMap::new(),
        schema: schema::Schema {
            comment: vec![],
            imports: BTreeMap::new(),
            declarations: vec![],
        },
    };

    // Populate the tree with all the schemas.
    for (namespace, (schema, _, _)) in schemas {
        insert_schema(&mut tree, namespace, schema);
    }

    // Write the code.
    let mut buffer = String::new();

    if !tree.children.is_empty() || !tree.schema.declarations.is_empty() {
        // The `unwrap` is safe because the `std::fmt::Write` impl for `String` is infallible.
        // For functions that take abstract parameters which implement `std::io::Read` or
        // `std::io::Write`, it's idiomatic to consume the reader or writer rather than borrowing
        // them (https://rust-lang.github.io/api-guidelines/interoperability.html
        // #generic-readerwriter-functions-take-r-read-and-w-write-by-value-c-rw-value). However,
        // we borrow them anyway since that allows us to pass the reference (after reborrowing)
        // to recursive calls, rather than building up and following chains of references.
        writeln!(
            &mut buffer,
            "\
// This file was automatically generated by Typical {typical_version}.
// Visit https://github.com/stepchowfun/typical for more information.

use std::{{
    cmp::min,
    io::{{self, BufRead, Error, ErrorKind, Write}},
}};

const MISSING_FIELDS_ERROR_MESSAGE: &str = \"Struct missing one or more required field(s).\";

pub trait Serialize {{
    fn size(&self) -> usize;

    fn serialize<T: Write>(&self, writer: T) -> io::Result<()>;
}}

pub trait Deserialize: Sized {{
    fn deserialize<T: BufRead>(reader: T) -> io::Result<Self>;
}}

fn zigzag_encode(value: i64) -> u64 {{
    i64::cast_unsigned(value >> 63_u32) ^ i64::cast_unsigned(value << 1_u32)
}}

fn zigzag_decode(value: u64) -> i64 {{
    u64::cast_signed(value >> 1_u32) ^ -u64::cast_signed(value & 1)
}}

fn varint_size_from_value(value: u64) -> usize {{
    match value {{
        0_u64..=127_u64 => 1,
        128_u64..=16_511_u64 => 2,
        16_512_u64..=2_113_663_u64 => 3,
        2_113_664_u64..=270_549_119_u64 => 4,
        270_549_120_u64..=34_630_287_487_u64 => 5,
        34_630_287_488_u64..=4_432_676_798_591_u64 => 6,
        4_432_676_798_592_u64..=567_382_630_219_903_u64 => 7,
        567_382_630_219_904_u64..=72_624_976_668_147_839_u64 => 8,
        72_624_976_668_147_840_u64..=18_446_744_073_709_551_615_u64 => 9,
    }}
}}

fn varint_size_from_first_byte(first_byte: u8) -> u32 {{
    first_byte.trailing_zeros() + 1
}}

fn serialize_varint<T: Write>(mut value: u64, writer: &mut T) -> io::Result<()> {{
    match value {{
        0_u64..=127_u64 => writer.write_all(&[((value << 1_u32) as u8) | 0b0000_0001]),
        128_u64..=16_511_u64 => {{
            value -= 128_u64;
            writer.write_all(&[
                ((value << 2_u32) as u8) | 0b0000_0010,
                (value >> 6_u32) as u8,
            ])
        }}
        16_512_u64..=2_113_663_u64 => {{
            value -= 16_512_u64;
            writer.write_all(&[
                ((value << 3_u32) as u8) | 0b0000_0100,
                (value >> 5_u32) as u8,
                (value >> 13_u32) as u8,
            ])
        }}
        2_113_664_u64..=270_549_119_u64 => {{
            value -= 2_113_664_u64;
            writer.write_all(&[
                ((value << 4_u32) as u8) | 0b0000_1000,
                (value >> 4_u32) as u8,
                (value >> 12_u32) as u8,
                (value >> 20_u32) as u8,
            ])
        }}
        270_549_120_u64..=34_630_287_487_u64 => {{
            value -= 270_549_120_u64;
            writer.write_all(&[
                ((value << 5_u32) as u8) | 0b0001_0000,
                (value >> 3_u32) as u8,
                (value >> 11_u32) as u8,
                (value >> 19_u32) as u8,
                (value >> 27_u32) as u8,
            ])
        }}
        34_630_287_488_u64..=4_432_676_798_591_u64 => {{
            value -= 34_630_287_488_u64;
            writer.write_all(&[
                ((value << 6_u32) as u8) | 0b0010_0000,
                (value >> 2_u32) as u8,
                (value >> 10_u32) as u8,
                (value >> 18_u32) as u8,
                (value >> 26_u32) as u8,
                (value >> 34_u32) as u8,
            ])
        }}
        4_432_676_798_592_u64..=567_382_630_219_903_u64 => {{
            value -= 4_432_676_798_592_u64;
            writer.write_all(&[
                ((value << 7_u32) as u8) | 0b0100_0000,
                (value >> 1_u32) as u8,
                (value >> 9_u32) as u8,
                (value >> 17_u32) as u8,
                (value >> 25_u32) as u8,
                (value >> 33_u32) as u8,
                (value >> 41_u32) as u8,
            ])
        }}
        567_382_630_219_904_u64..=72_624_976_668_147_839_u64 => {{
            value -= 567_382_630_219_904_u64;
            writer.write_all(&[
                0b1000_0000,
                value as u8,
                (value >> 8_u32) as u8,
                (value >> 16_u32) as u8,
                (value >> 24_u32) as u8,
                (value >> 32_u32) as u8,
                (value >> 40_u32) as u8,
                (value >> 48_u32) as u8,
            ])
        }}
        72_624_976_668_147_840_u64..=18_446_744_073_709_551_615_u64 => {{
            value -= 72_624_976_668_147_840_u64;
            writer.write_all(&[
                0b0000_0000,
                value as u8,
                (value >> 8_u32) as u8,
                (value >> 16_u32) as u8,
                (value >> 24_u32) as u8,
                (value >> 32_u32) as u8,
                (value >> 40_u32) as u8,
                (value >> 48_u32) as u8,
                (value >> 56_u32) as u8,
            ])
        }}
    }}
}}

fn deserialize_varint<T: BufRead>(reader: &mut T) -> io::Result<u64> {{
    let mut first_byte_buffer = [0; 1];
    reader.read_exact(&mut first_byte_buffer[..])?;
    let first_byte = first_byte_buffer[0];
    let size_minus_one = first_byte.trailing_zeros();

    let mut remaining_bytes_buffer = [0; 8];
    reader.read_exact(&mut remaining_bytes_buffer[0..size_minus_one as usize])?;
    let remaining_bytes_value = u64::from_le_bytes(remaining_bytes_buffer);

    match size_minus_one {{
        0 => Ok(u64::from(first_byte >> 1_u32)),
        1 => Ok(128_u64 + u64::from(first_byte >> 2_u32) + (remaining_bytes_value << 6_u32)),
        2 => Ok(16_512_u64 + u64::from(first_byte >> 3_u32) + (remaining_bytes_value << 5_u32)),
        3 => Ok(2_113_664_u64 + u64::from(first_byte >> 4_u32) + (remaining_bytes_value << 4_u32)),
        4 => {{
            Ok(270_549_120_u64 + u64::from(first_byte >> 5_u32) + (remaining_bytes_value << 3_u32))
        }}
        5 => Ok(34_630_287_488_u64
            + u64::from(first_byte >> 6_u32)
            + (remaining_bytes_value << 2_u32)),
        6 => Ok(4_432_676_798_592_u64
            + u64::from(first_byte >> 7_u32)
            + (remaining_bytes_value << 1_u32)),
        7 => Ok(567_382_630_219_904_u64 + remaining_bytes_value),
        _ => Ok(72_624_976_668_147_840_u64.wrapping_add(remaining_bytes_value)),
    }}
}}

fn field_header_size(index: u64, payload_size: usize, integer_encoded: bool) -> usize {{
    match payload_size {{
        0 => varint_size_from_value((index << 2_u32) | 0b00),
        8 => varint_size_from_value((index << 2_u32) | 0b01),
        size => {{
            if integer_encoded {{
                varint_size_from_value((index << 2_u32) | 0b10)
            }} else {{
                varint_size_from_value((index << 2_u32) | 0b11)
                    + varint_size_from_value(size as u64)
            }}
        }}
    }}
}}

fn serialize_field_header<T: Write>(
    writer: &mut T,
    index: u64,
    payload_size: usize,
    integer_encoded: bool,
) -> io::Result<()> {{
    match payload_size {{
        0 => serialize_varint((index << 2_u32) | 0b00, writer),
        8 => serialize_varint((index << 2_u32) | 0b01, writer),
        size => {{
            if integer_encoded {{
                serialize_varint((index << 2_u32) | 0b10, writer)
            }} else {{
                serialize_varint((index << 2_u32) | 0b11, writer)?;
                serialize_varint(size as u64, writer)
            }}
        }}
    }}
}}

fn deserialize_field_header<T: BufRead>(reader: &mut T) -> io::Result<(u64, usize)> {{
    let tag = deserialize_varint(&mut *reader)?;

    let index = tag >> 2_u32;

    let size = match tag & 0b11 {{
        0b00 => 0,
        0b01 => 8,
        0b10 => {{
            let buffer = (&mut *reader).fill_buf()?;

            if buffer.is_empty() {{
                return Err(Error::new(
                    ErrorKind::UnexpectedEof,
                    \"Error decoding field.\",
                ));
            }}

            varint_size_from_first_byte(buffer[0]) as usize
        }}
        _ => deserialize_varint(&mut *reader)? as usize,
    }};

    Ok((index, size))
}}

fn skip<T: BufRead>(reader: &mut T, mut amount: usize) -> io::Result<()> {{
    while amount > 0 {{
        let buffer = reader.fill_buf()?;
        let num_bytes_to_consume = min(buffer.len(), amount);
        reader.consume(num_bytes_to_consume);
        amount -= num_bytes_to_consume;
    }}

    Ok(())
}}

fn finish<T: BufRead>(reader: &mut T) -> io::Result<()> {{
    loop {{
        let buffer = reader.fill_buf()?;

        if buffer.is_empty() {{
            return Ok(());
        }}

        let buffer_size = buffer.len();
        reader.consume(buffer_size);
    }}
}}",
        )
        .unwrap();

        // The `unwrap` is safe because the `std::fmt::Write` impl for `String` is infallible.
        writeln!(&mut buffer).unwrap();

        // The `unwrap` is safe because the `std::fmt::Write` impl for `String` is infallible.
        write_module_contents(
            &mut buffer,
            0,
            &schema::Namespace { components: vec![] },
            &tree.children,
            &tree.schema,
        )
        .unwrap();
    }

    buffer
}

// Insert a schema into a module.
fn insert_schema(module: &mut Module, namespace: &schema::Namespace, schema: &schema::Schema) {
    let mut iter = namespace.components.iter();

    if let Some(head) = iter.next() {
        if let Some(child) = module.children.get_mut(head) {
            insert_schema(
                child,
                &schema::Namespace {
                    components: iter.cloned().collect(),
                },
                schema,
            );
        } else {
            let mut child = Module {
                children: BTreeMap::new(),
                schema: schema::Schema {
                    comment: vec![],
                    imports: BTreeMap::new(),
                    declarations: vec![],
                },
            };

            insert_schema(
                &mut child,
                &schema::Namespace {
                    components: iter.cloned().collect(),
                },
                schema,
            );

            module.children.insert(head.clone(), child);
        }
    } else {
        module.schema = schema.clone();
    }
}

// Write a module, including a trailing line break.
fn write_module<T: Write>(
    buffer: &mut T,
    indentation: usize,
    namespace: &schema::Namespace,
    name: &Identifier,
    module: &Module,
) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    write!(buffer, "pub mod ")?;
    write_identifier(buffer, name, Snake, None)?;
    writeln!(buffer, " {{")?;

    let mut new_namespace = namespace.clone();
    new_namespace.components.push(name.clone());

    write_module_contents(
        buffer,
        indentation + 1,
        &new_namespace,
        &module.children,
        &module.schema,
    )?;

    write_indentation(buffer, indentation)?;
    writeln!(buffer, "}}")?;

    Ok(())
}

// Write the contents of a module, including a trailing line break if there was anything to render.
fn write_module_contents<T: Write>(
    buffer: &mut T,
    indentation: usize,
    namespace: &schema::Namespace,
    children: &BTreeMap<Identifier, Module>,
    schema: &schema::Schema,
) -> Result<(), fmt::Error> {
    let schema_empty = schema.declarations.is_empty();

    for (i, (child_name, child)) in children.iter().enumerate() {
        write_module(buffer, indentation, namespace, child_name, child)?;

        if i < children.len() - 1 || !schema_empty {
            writeln!(buffer)?;
        }
    }

    write_schema(buffer, indentation, namespace, schema)?;

    Ok(())
}

// Write a schema, including a trailing line break if there was anything to render.
#[allow(clippy::too_many_lines)]
fn write_schema<T: Write>(
    buffer: &mut T,
    indentation: usize,
    namespace: &schema::Namespace,
    schema: &schema::Schema,
) -> Result<(), fmt::Error> {
    // Construct a map from import name to namespace.
    let mut imports = BTreeMap::new();
    for (name, import) in &schema.imports {
        // The `unwrap` is safe due to [ref:namespace_populated].
        imports.insert(name.clone(), import.namespace.clone().unwrap());
    }

    // Write the declarations.
    let mut iter = schema.declarations.iter().peekable();
    while let Some(declaration) = iter.next() {
        match &declaration.variant {
            schema::DeclarationVariant::Struct => {
                write_struct(
                    buffer,
                    indentation,
                    &imports,
                    namespace,
                    &declaration.name,
                    &declaration.fields,
                    Atlas,
                )?;

                writeln!(buffer)?;

                write_struct(
                    buffer,
                    indentation,
                    &imports,
                    namespace,
                    &declaration.name,
                    &declaration.fields,
                    Out,
                )?;

                writeln!(buffer)?;

                write_struct(
                    buffer,
                    indentation,
                    &imports,
                    namespace,
                    &declaration.name,
                    &declaration.fields,
                    In,
                )?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_supers(buffer, indentation)?;
                write!(buffer, "Serialize for ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                writeln!(buffer, " {{")?;
                write_size_function(buffer, indentation + 1)?;
                writeln!(buffer)?;
                write_serialize_function(buffer, indentation + 1)?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_supers(buffer, indentation)?;
                write!(buffer, "Deserialize for ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                write_deserialize_function(buffer, indentation + 1)?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl From<")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                write!(buffer, "> for ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                write!(buffer, "fn from(message: ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                writeln!(buffer, ") -> Self {{")?;
                write_indentation(buffer, indentation + 2)?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                for field in &declaration.fields {
                    match field.rule {
                        schema::Rule::Asymmetric => {
                            write_indentation(buffer, indentation + 3)?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(buffer, ": Some(message.")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                        }
                        schema::Rule::Optional => {
                            write_indentation(buffer, indentation + 3)?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(buffer, ": message.")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(buffer, ".map(|payload| payload")?;
                        }
                        schema::Rule::Required => {
                            write_indentation(buffer, indentation + 3)?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(buffer, ": message.")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                        }
                    }
                    write_into_invocation(buffer, &field.r#type.variant)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            writeln!(buffer, "),")?;
                        }
                        schema::Rule::Required => {
                            writeln!(buffer, ",")?;
                        }
                    }
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                write!(buffer, "pub fn atlas(&self) -> ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, " {{")?;
                if !&declaration.fields.is_empty() {
                    for field in &declaration.fields {
                        write_indentation(buffer, indentation + 2)?;
                        write!(buffer, "let _")?;
                        write_identifier(buffer, &field.name, Snake, None)?;
                        match field.rule {
                            schema::Rule::Asymmetric | schema::Rule::Required => {
                                write!(buffer, " = {{ let payload = &self.")?;
                                write_identifier(buffer, &field.name, Snake, None)?;
                                write!(buffer, "; ")?;
                                write_atlas_calculation(
                                    buffer,
                                    indentation,
                                    &field.r#type.variant,
                                    true,
                                )?;
                                writeln!(buffer, " }};")?;
                            }
                            schema::Rule::Optional => {
                                write!(buffer, " = self.")?;
                                write_identifier(buffer, &field.name, Snake, None)?;
                                write!(buffer, ".as_ref().map(|payload| ")?;
                                write_atlas_calculation(
                                    buffer,
                                    indentation,
                                    &field.r#type.variant,
                                    true,
                                )?;
                                writeln!(buffer, ");")?;
                            }
                        }
                    }
                    writeln!(buffer)?;
                }
                write_indentation(buffer, indentation + 2)?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 3)?;
                write!(buffer, "_size:")?;
                if declaration.fields.is_empty() {
                    write!(buffer, " 0")?;
                }
                let mut first = true;
                for field in &declaration.fields {
                    writeln!(buffer)?;
                    write_indentation(buffer, indentation + 4)?;
                    if first {
                        first = false;
                    } else {
                        write!(buffer, "+ ")?;
                    }
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Required => {
                            write!(buffer, "{{ let payload_atlas = &_")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(buffer, "; let payload_size = ")?;
                        }
                        schema::Rule::Optional => {
                            write!(buffer, "_")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(
                                buffer,
                                ".as_ref().map_or(0_usize, |payload_atlas| {{ let payload_size = ",
                            )?;
                        }
                    }
                    write_atlas_lookup(buffer, &field.r#type.variant)?;
                    write!(buffer, "; ")?;
                    write_supers(buffer, indentation)?;
                    write!(
                        buffer,
                        "field_header_size({}_u64, payload_size, {}) + payload_size }}",
                        field.index,
                        integer_encoded(&field.r#type),
                    )?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Required => {}
                        schema::Rule::Optional => {
                            write!(buffer, ")")?;
                        }
                    }
                }
                writeln!(buffer, ",")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 3)?;
                    write_identifier(buffer, &field.name, Snake, None)?;
                    write!(buffer, ": _")?;
                    write_identifier(buffer, &field.name, Snake, None)?;
                    writeln!(buffer, ",")?;
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "pub fn serialize_with_atlas<T: ::std::io::Write>(")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "&self,")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "writer: &mut T,")?;
                write_indentation(buffer, indentation + 2)?;
                write!(buffer, "atlas: &")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, ",")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, ") -> ::std::io::Result<()> {{")?;
                for field in &declaration.fields {
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Required => {
                            write_indentation(buffer, indentation + 2)?;
                            writeln!(buffer, "{{")?;
                            write_indentation(buffer, indentation + 3)?;
                            write!(buffer, "let payload = &self.")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            writeln!(buffer, ";")?;
                            write_indentation(buffer, indentation + 3)?;
                            write!(buffer, "let payload_atlas = &atlas.")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            writeln!(buffer, ";")?;
                        }
                        schema::Rule::Optional => {
                            write_indentation(buffer, indentation + 2)?;
                            write!(
                                buffer,
                                "if let (Some(payload), Some(payload_atlas)) = (&self.",
                            )?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            write!(buffer, ", &atlas.")?;
                            write_identifier(buffer, &field.name, Snake, None)?;
                            writeln!(buffer, ") {{")?;
                        }
                    }
                    write_indentation(buffer, indentation + 3)?;
                    write_supers(buffer, indentation)?;
                    write!(
                        buffer,
                        "serialize_field_header(writer, {}_u64, ",
                        field.index,
                    )?;
                    write_atlas_lookup(buffer, &field.r#type.variant)?;
                    writeln!(buffer, ", {})?;", integer_encoded(&field.r#type))?;
                    write_serialization_invocation(
                        buffer,
                        indentation + 3,
                        indentation,
                        &field.r#type.variant,
                        true,
                    )?;
                    write_indentation(buffer, indentation + 2)?;
                    writeln!(buffer, "}}")?;
                    writeln!(buffer)?;
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "Ok(())")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(
                    buffer,
                    "pub fn deserialize_from_reader_ref<T: ::std::io::BufRead>(",
                )?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "reader: &mut T,")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, ") -> ::std::io::Result<Self> {{")?;
                if !&declaration.fields.is_empty() {
                    for field in &declaration.fields {
                        write_indentation(buffer, indentation + 2)?;
                        write!(buffer, "let mut _")?;
                        write_identifier(buffer, &field.name, Snake, None)?;
                        write!(buffer, ": Option<")?;
                        write_type(buffer, &imports, namespace, &field.r#type.variant, In)?;
                        writeln!(buffer, "> = None;")?;
                    }
                    writeln!(buffer)?;
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "loop {{")?;
                write_indentation(buffer, indentation + 3)?;
                write!(buffer, "let (index, payload_size) = match ")?;
                write_supers(buffer, indentation)?;
                writeln!(buffer, "deserialize_field_header(&mut *reader) {{")?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "Ok(header) => header,")?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "Err(err) => {{")?;
                write_indentation(buffer, indentation + 5)?;
                writeln!(
                    buffer,
                    "if let std::io::ErrorKind::UnexpectedEof = err.kind() {{",
                )?;
                write_indentation(buffer, indentation + 6)?;
                writeln!(buffer, "break;")?;
                write_indentation(buffer, indentation + 5)?;
                writeln!(buffer, "}}")?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 5)?;
                writeln!(buffer, "return Err(err);")?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "}};")?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(
                    buffer,
                    "let mut sub_reader = ::std::io::Read::take(&mut *reader, \
                        payload_size as u64);",
                )?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "match index {{")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 4)?;
                    writeln!(buffer, "{} => {{", field.index)?;
                    write_deserialization_invocation(
                        buffer,
                        indentation + 5,
                        indentation,
                        &imports,
                        namespace,
                        &field.r#type.variant,
                        true,
                    )?;
                    write_indentation(buffer, indentation + 5)?;
                    write!(buffer, "_")?;
                    write_identifier(buffer, &field.name, Snake, None)?;
                    writeln!(buffer, ".get_or_insert(payload);")?;
                    write_indentation(buffer, indentation + 4)?;
                    writeln!(buffer, "}}")?;
                }
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "_ => {{")?;
                write_indentation(buffer, indentation + 5)?;
                write_supers(buffer, indentation)?;
                writeln!(buffer, "skip(&mut sub_reader, payload_size)?;")?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                writeln!(buffer)?;
                // The logic below ensures that all required fields have been parsed.
                // [tag:required_fields_present]
                if declaration.fields.iter().any(|field| match field.rule {
                    schema::Rule::Asymmetric | schema::Rule::Optional => false,
                    schema::Rule::Required => true,
                }) {
                    write_indentation(buffer, indentation + 2)?;
                    write!(buffer, "if ")?;
                    first = true;
                    for field in &declaration.fields {
                        match field.rule {
                            schema::Rule::Asymmetric | schema::Rule::Optional => {}
                            schema::Rule::Required => {
                                if first {
                                    first = false;
                                } else {
                                    writeln!(buffer)?;
                                    write_indentation(buffer, indentation + 3)?;
                                    write!(buffer, "|| ")?;
                                }
                                write!(buffer, "_")?;
                                write_identifier(buffer, &field.name, Snake, None)?;
                                write!(buffer, ".is_none()")?;
                            }
                        }
                    }
                    writeln!(buffer, " {{")?;
                    write_indentation(buffer, indentation + 3)?;
                    writeln!(buffer, "return Err(::std::io::Error::new(")?;
                    write_indentation(buffer, indentation + 4)?;
                    writeln!(buffer, "::std::io::ErrorKind::InvalidData,")?;
                    write_indentation(buffer, indentation + 4)?;
                    write_supers(buffer, indentation)?;
                    writeln!(buffer, "MISSING_FIELDS_ERROR_MESSAGE,")?;
                    write_indentation(buffer, indentation + 3)?;
                    writeln!(buffer, "));")?;
                    write_indentation(buffer, indentation + 2)?;
                    writeln!(buffer, "}}")?;
                    writeln!(buffer)?;
                }
                write_indentation(buffer, indentation + 2)?;
                write!(buffer, "Ok(")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 3)?;
                    write_identifier(buffer, &field.name, Snake, None)?;
                    write!(buffer, ": _")?;
                    write_identifier(buffer, &field.name, Snake, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {}
                        schema::Rule::Required => {
                            // This `unwrap` is safe due to [ref:required_fields_present].
                            write!(buffer, ".unwrap()")?;
                        }
                    }
                    writeln!(buffer, ",")?;
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}})")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "pub fn size(&self) -> usize {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "self._size")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;
            }
            schema::DeclarationVariant::Choice => {
                write_choice(
                    buffer,
                    indentation,
                    &imports,
                    namespace,
                    &declaration.name,
                    &declaration.fields,
                    Atlas,
                )?;

                writeln!(buffer)?;

                write_choice(
                    buffer,
                    indentation,
                    &imports,
                    namespace,
                    &declaration.name,
                    &declaration.fields,
                    Out,
                )?;

                writeln!(buffer)?;

                write_choice(
                    buffer,
                    indentation,
                    &imports,
                    namespace,
                    &declaration.name,
                    &declaration.fields,
                    In,
                )?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_supers(buffer, indentation)?;
                write!(buffer, "Serialize for ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                writeln!(buffer, " {{")?;
                write_size_function(buffer, indentation + 1)?;
                writeln!(buffer)?;
                write_serialize_function(buffer, indentation + 1)?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_supers(buffer, indentation)?;
                write!(buffer, "Deserialize for ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                write_deserialize_function(buffer, indentation + 1)?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl From<")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                write!(buffer, "> for ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                write!(buffer, "fn from(message: ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                writeln!(buffer, ") -> Self {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "match message {{")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 3)?;
                    write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                    write!(buffer, "::")?;
                    write_identifier(buffer, &field.name, Pascal, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                write!(buffer, "(fallback) => ")?;
                            } else {
                                write!(buffer, "(payload, fallback) => ")?;
                            }
                        }
                        schema::Rule::Required => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                write!(buffer, " => ")?;
                            } else {
                                write!(buffer, "(payload) => ")?;
                            }
                        }
                    }
                    write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                    write!(buffer, "::")?;
                    write_identifier(buffer, &field.name, Pascal, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Required => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                writeln!(buffer, ",")?;
                            } else {
                                write!(buffer, "(payload")?;
                                write_into_invocation(buffer, &field.r#type.variant)?;
                                writeln!(buffer, "),")?;
                            }
                        }
                        schema::Rule::Optional => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                writeln!(buffer, "(Box::new((*fallback).into())),")?;
                            } else {
                                write!(buffer, "(payload")?;
                                write_into_invocation(buffer, &field.r#type.variant)?;
                                writeln!(buffer, ", Box::new((*fallback).into())),")?;
                            }
                        }
                    }
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                write!(buffer, "pub fn atlas(&self) -> ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 2)?;
                // [tag:empty_enum_ref_match] We match on `self*` instead of `self` due to
                // https://github.com/rust-lang/rust/issues/78123.
                writeln!(buffer, "match *self {{")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 3)?;
                    write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                    write!(buffer, "::")?;
                    write_identifier(buffer, &field.name, Pascal, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                writeln!(buffer, "(ref fallback) => {{")?;
                                write_indentation(buffer, indentation + 4)?;
                                writeln!(buffer, "let payload = &();")?;
                            } else {
                                writeln!(buffer, "(ref payload, ref fallback) => {{")?;
                            }
                        }
                        schema::Rule::Required => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                writeln!(buffer, " => {{")?;
                                write_indentation(buffer, indentation + 4)?;
                                writeln!(buffer, "let payload = &();")?;
                            } else {
                                writeln!(buffer, "(ref payload) => {{")?;
                            }
                        }
                    }
                    write_indentation(buffer, indentation + 4)?;
                    write!(buffer, "let payload_atlas = ")?;
                    write_atlas_calculation(buffer, indentation, &field.r#type.variant, true)?;
                    writeln!(buffer, ";")?;
                    write_indentation(buffer, indentation + 4)?;
                    write!(
                        buffer,
                        "let payload_size = {{ let payload_atlas = &payload_atlas; ",
                    )?;
                    write_atlas_lookup(buffer, &field.r#type.variant)?;
                    writeln!(buffer, " }};")?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            write_indentation(buffer, indentation + 4)?;
                            writeln!(buffer, "let fallback_atlas = fallback.atlas();")?;
                            write_indentation(buffer, indentation + 4)?;
                            write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                            write!(buffer, "::")?;
                            write_identifier(buffer, &field.name, Pascal, None)?;
                            write!(buffer, "(")?;
                            write_supers(buffer, indentation)?;
                            writeln!(
                                buffer,
                                "field_header_size({}_u64, payload_size, {}) + \
                                    payload_size + fallback_atlas.size(), \
                                    payload_atlas, Box::new(fallback_atlas))",
                                field.index,
                                integer_encoded(&field.r#type),
                            )?;
                        }
                        schema::Rule::Required => {
                            write_indentation(buffer, indentation + 4)?;
                            write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                            write!(buffer, "::")?;
                            write_identifier(buffer, &field.name, Pascal, None)?;
                            write!(buffer, "(")?;
                            write_supers(buffer, indentation)?;
                            writeln!(
                                buffer,
                                "field_header_size({}_u64, payload_size, {}) + payload_size, \
                                    payload_atlas)",
                                field.index,
                                integer_encoded(&field.r#type),
                            )?;
                        }
                    }
                    write_indentation(buffer, indentation + 3)?;
                    writeln!(buffer, "}}")?;
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "pub fn serialize_with_atlas<T: ::std::io::Write>(")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "&self,")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "writer: &mut T,")?;
                write_indentation(buffer, indentation + 2)?;
                write!(buffer, "atlas: &")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, ",")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, ") -> ::std::io::Result<()> {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "match (self, atlas) {{")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 3)?;
                    write!(buffer, "(")?;
                    write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
                    write!(buffer, "::")?;
                    write_identifier(buffer, &field.name, Pascal, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                write!(buffer, "(fallback), ")?;
                            } else {
                                write!(buffer, "(payload, fallback), ")?;
                            }
                        }
                        schema::Rule::Required => {
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                write!(buffer, ", ")?;
                            } else {
                                write!(buffer, "(payload), ")?;
                            }
                        }
                    }
                    write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                    write!(buffer, "::")?;
                    write_identifier(buffer, &field.name, Pascal, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            writeln!(buffer, "(_, payload_atlas, fallback_atlas)) => {{")?;
                        }
                        schema::Rule::Required => {
                            writeln!(buffer, "(_, payload_atlas)) => {{")?;
                        }
                    }
                    write_indentation(buffer, indentation + 4)?;
                    write_supers(buffer, indentation)?;
                    write!(
                        buffer,
                        "serialize_field_header(writer, {}_u64, ",
                        field.index,
                    )?;
                    write_atlas_lookup(buffer, &field.r#type.variant)?;
                    writeln!(buffer, ", {})?;", integer_encoded(&field.r#type))?;
                    write_serialization_invocation(
                        buffer,
                        indentation + 4,
                        indentation,
                        &field.r#type.variant,
                        true,
                    )?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            write_indentation(buffer, indentation + 4)?;
                            writeln!(
                                buffer,
                                "fallback.serialize_with_atlas(writer, fallback_atlas)",
                            )?;
                        }
                        schema::Rule::Required => {
                            write_indentation(buffer, indentation + 4)?;
                            writeln!(buffer, "Ok(())")?;
                        }
                    }
                    write_indentation(buffer, indentation + 3)?;
                    writeln!(buffer, "}}")?;
                }
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "(_, _) => panic!(),")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(
                    buffer,
                    "pub fn deserialize_from_reader_ref<T: ::std::io::BufRead>(",
                )?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "reader: &mut T,")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, ") -> ::std::io::Result<Self> {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "loop {{")?;
                write_indentation(buffer, indentation + 3)?;
                write!(buffer, "let (index, payload_size) = ")?;
                write_supers(buffer, indentation)?;
                writeln!(buffer, "deserialize_field_header(&mut *reader)?;")?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(
                    buffer,
                    "let mut sub_reader = ::std::io::Read::take(&mut *reader, \
                        payload_size as u64);",
                )?;
                writeln!(buffer)?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "match index {{")?;
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 4)?;
                    writeln!(buffer, "{} => {{", field.index)?;
                    write_deserialization_invocation(
                        buffer,
                        indentation + 5,
                        indentation,
                        &imports,
                        namespace,
                        &field.r#type.variant,
                        true,
                    )?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Required => {
                            write_indentation(buffer, indentation + 5)?;
                            write_supers(buffer, indentation)?;
                            writeln!(buffer, "finish(&mut *reader)?;")?;
                            write_indentation(buffer, indentation + 5)?;
                            write!(buffer, "return Ok(")?;
                            write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                            write!(buffer, "::")?;
                            write_identifier(buffer, &field.name, Pascal, None)?;
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                writeln!(buffer, ");")?;
                            } else {
                                writeln!(buffer, "(payload));")?;
                            }
                        }
                        schema::Rule::Optional => {
                            write_indentation(buffer, indentation + 5)?;
                            write!(buffer, "let fallback = Box::new(")?;
                            write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                            writeln!(buffer, "::deserialize_from_reader_ref(&mut *reader)?);")?;
                            write_indentation(buffer, indentation + 5)?;
                            write!(buffer, "return Ok(")?;
                            write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
                            write!(buffer, "::")?;
                            write_identifier(buffer, &field.name, Pascal, None)?;
                            if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
                                writeln!(buffer, "(fallback));")?;
                            } else {
                                writeln!(buffer, "(payload, fallback));")?;
                            }
                        }
                    }
                    write_indentation(buffer, indentation + 4)?;
                    writeln!(buffer, "}}")?;
                }
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "_ => {{")?;
                write_indentation(buffer, indentation + 5)?;
                write_supers(buffer, indentation)?;
                writeln!(buffer, "skip(&mut sub_reader, payload_size)?;")?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;

                writeln!(buffer)?;

                write_indentation(buffer, indentation)?;
                write!(buffer, "impl ")?;
                write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                writeln!(buffer, " {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "pub fn size(&self) -> usize {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "match *self {{")?; // [ref:empty_enum_ref_match]
                for field in &declaration.fields {
                    write_indentation(buffer, indentation + 3)?;
                    write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
                    write!(buffer, "::")?;
                    write_identifier(buffer, &field.name, Pascal, None)?;
                    match field.rule {
                        schema::Rule::Asymmetric | schema::Rule::Optional => {
                            writeln!(buffer, "(ref size, _, _) => *size,")?;
                        }
                        schema::Rule::Required => {
                            writeln!(buffer, "(ref size, _) => *size,")?;
                        }
                    }
                }
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;
            }
        }

        if iter.peek().is_some() {
            writeln!(buffer)?;
        }
    }

    Ok(())
}

// Write a struct, including a trailing line break.
fn write_struct<T: Write>(
    buffer: &mut T,
    indentation: usize,
    imports: &BTreeMap<Identifier, schema::Namespace>,
    namespace: &schema::Namespace,
    name: &Identifier,
    fields: &[schema::Field],
    direction: Direction,
) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    writeln!(buffer, "#[derive({})]", TRAITS_TO_DERIVE.join(", "))?;
    write_indentation(buffer, indentation)?;
    write!(buffer, "pub struct ")?;
    write_identifier(buffer, name, Pascal, Some(direction))?;
    writeln!(buffer, " {{")?;

    match direction {
        Direction::Atlas => {
            write_indentation(buffer, indentation + 1)?;
            writeln!(buffer, "pub _size: usize,")?;
        }
        Direction::In | Direction::Out => {}
    }

    for field in fields {
        write_indentation(buffer, indentation + 1)?;
        write!(buffer, "pub ")?;
        write_identifier(buffer, &field.name, Snake, None)?;
        write!(buffer, ": ")?;
        match field.rule {
            schema::Rule::Asymmetric => match direction {
                Direction::Atlas | Direction::Out => {}
                Direction::In => {
                    write!(buffer, "Option<")?;
                }
            },
            schema::Rule::Optional => {
                write!(buffer, "Option<")?;
            }
            schema::Rule::Required => {}
        }
        write_type(buffer, imports, namespace, &field.r#type.variant, direction)?;
        match field.rule {
            schema::Rule::Asymmetric => match direction {
                Direction::Atlas | Direction::Out => {}
                Direction::In => {
                    write!(buffer, ">")?;
                }
            },
            schema::Rule::Optional => {
                write!(buffer, ">")?;
            }
            schema::Rule::Required => {}
        }
        writeln!(buffer, ",")?;
    }

    write_indentation(buffer, indentation)?;
    writeln!(buffer, "}}")?;

    Ok(())
}

// Write a choice, including a trailing line break.
fn write_choice<T: Write>(
    buffer: &mut T,
    indentation: usize,
    imports: &BTreeMap<Identifier, schema::Namespace>,
    namespace: &schema::Namespace,
    name: &Identifier,
    fields: &[schema::Field],
    direction: Direction,
) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    writeln!(buffer, "#[derive({})]", TRAITS_TO_DERIVE.join(", "))?;
    write_indentation(buffer, indentation)?;
    write!(buffer, "pub enum ")?;
    write_identifier(buffer, name, Pascal, Some(direction))?;
    writeln!(buffer, " {{")?;

    for field in fields {
        write_indentation(buffer, indentation + 1)?;
        write_identifier(buffer, &field.name, Pascal, None)?;

        let size = match direction {
            Direction::Atlas => true,
            Direction::In | Direction::Out => false,
        };

        let payload = match direction {
            Direction::Atlas => true,
            Direction::In | Direction::Out => {
                !matches!(field.r#type.variant, schema::TypeVariant::Unit)
            }
        };

        let fallback = match field.rule {
            schema::Rule::Asymmetric => match direction {
                Direction::Atlas | Direction::Out => true,
                Direction::In => false,
            },
            schema::Rule::Optional => true,
            schema::Rule::Required => false,
        };

        if size || payload || fallback {
            write!(buffer, "(")?;
        }

        if size {
            write!(buffer, "usize")?;

            if payload || fallback {
                write!(buffer, ", ")?;
            }
        }

        if payload {
            write_type(buffer, imports, namespace, &field.r#type.variant, direction)?;

            if fallback {
                write!(buffer, ", ")?;
            }
        }

        if fallback {
            write!(buffer, "Box<")?;
            write_identifier(buffer, name, Pascal, Some(direction))?;
            write!(buffer, ">")?;
        }

        if size || payload || fallback {
            write!(buffer, ")")?;
        }

        writeln!(buffer, ",")?;
    }

    write_indentation(buffer, indentation)?;
    writeln!(buffer, "}}")?;

    Ok(())
}

fn write_size_function<T: Write>(buffer: &mut T, indentation: usize) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    writeln!(buffer, "fn size(&self) -> usize {{")?;
    write_indentation(buffer, indentation + 1)?;
    writeln!(buffer, "self.atlas().size()")?;
    write_indentation(buffer, indentation)?;
    writeln!(buffer, "}}")
}

// Write the function to serialize a message.
fn write_serialize_function<T: Write>(
    buffer: &mut T,
    indentation: usize,
) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    writeln!(
        buffer,
        "fn serialize<T: ::std::io::Write>(&self, mut writer: T) -> ::std::io::Result<()> {{",
    )?;
    write_indentation(buffer, indentation + 1)?;
    writeln!(buffer, "let atlas = self.atlas();")?;
    write_indentation(buffer, indentation + 1)?;
    writeln!(buffer, "self.serialize_with_atlas(&mut writer, &atlas)")?;
    write_indentation(buffer, indentation)?;
    writeln!(buffer, "}}")
}

// Write the function to deserialize a message.
fn write_deserialize_function<T: Write>(
    buffer: &mut T,
    indentation: usize,
) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    writeln!(
        buffer,
        "fn deserialize<T: ::std::io::BufRead>(mut reader: T) -> ::std::io::Result<Self> {{",
    )?;
    write_indentation(buffer, indentation + 1)?;
    writeln!(buffer, "Self::deserialize_from_reader_ref(&mut reader)")?;
    write_indentation(buffer, indentation)?;
    writeln!(buffer, "}}")
}

// Write a type.
fn write_type<T: Write>(
    buffer: &mut T,
    imports: &BTreeMap<Identifier, schema::Namespace>,
    namespace: &schema::Namespace,
    type_variant: &schema::TypeVariant,
    direction: Direction,
) -> Result<(), fmt::Error> {
    match type_variant {
        schema::TypeVariant::Array(inner_type) => match direction {
            Direction::Atlas => match &inner_type.variant {
                schema::TypeVariant::Array(_)
                | schema::TypeVariant::Bytes
                | schema::TypeVariant::Custom(_, _)
                | schema::TypeVariant::String => {
                    write!(buffer, "(usize, Vec<")?;
                    write_type(buffer, imports, namespace, &inner_type.variant, direction)?;
                    write!(buffer, ">)")?;
                }
                schema::TypeVariant::Bool
                | schema::TypeVariant::F64
                | schema::TypeVariant::S64
                | schema::TypeVariant::U64
                | schema::TypeVariant::Unit => {
                    write!(buffer, "usize")?;
                }
            },
            Direction::In | Direction::Out => {
                write!(buffer, "Vec<")?;
                write_type(buffer, imports, namespace, &inner_type.variant, direction)?;
                write!(buffer, ">")?;
            }
        },
        schema::TypeVariant::Bool => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "bool")?;
            }
        },
        schema::TypeVariant::Bytes => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "Vec<u8>")?;
            }
        },
        schema::TypeVariant::Custom(import, name) => {
            let type_namespace = schema::Namespace {
                components: import.as_ref().map_or_else(
                    || namespace.components.clone(),
                    |import| imports[import].components.clone(),
                ),
            };

            let (relative_type_namespace, ancestors) =
                relativize_namespace(&type_namespace, namespace);

            write_supers(buffer, ancestors)?;

            for component in relative_type_namespace.components {
                write_identifier(buffer, &component, Snake, None)?;
                write!(buffer, "::")?;
            }

            write_identifier(buffer, name, Pascal, Some(direction))?;
        }
        schema::TypeVariant::F64 => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "f64")?;
            }
        },
        schema::TypeVariant::S64 => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "i64")?;
            }
        },
        schema::TypeVariant::String => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "String")?;
            }
        },
        schema::TypeVariant::U64 => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "u64")?;
            }
        },
        schema::TypeVariant::Unit => match direction {
            Direction::Atlas => {
                write!(buffer, "usize")?;
            }
            Direction::In | Direction::Out => {
                write!(buffer, "()")?;
            }
        },
    }

    Ok(())
}

// Write an identifier with an optional direction suffix in a way that Rust will be happy with.
fn write_identifier<T: Write>(
    buffer: &mut T,
    identifier: &Identifier,
    case: CaseConvention,
    suffix: Option<Direction>,
) -> Result<(), fmt::Error> {
    let identifier_with_suffix = suffix.map_or_else(
        || identifier.clone(),
        |suffix| {
            identifier.join(
                &match suffix {
                    Direction::Atlas => "Atlas",
                    Direction::In => "In",
                    Direction::Out => "Out",
                }
                .into(),
            )
        },
    );

    let converted_identifier = match case {
        CaseConvention::Pascal => identifier_with_suffix.pascal_case(),
        CaseConvention::Snake => identifier_with_suffix.snake_case(),
    };

    if RUST_KEYWORDS
        .iter()
        .any(|keyword| converted_identifier == *keyword)
    {
        write!(buffer, "r#")?;
    }

    write!(buffer, "{converted_identifier}")?;

    Ok(())
}

// Write the given level of indentation.
fn write_indentation<T: Write>(buffer: &mut T, indentation: usize) -> Result<(), fmt::Error> {
    for _ in 0..indentation {
        write!(buffer, "{INDENTATION}")?;
    }

    Ok(())
}

// Write a series of `super::super::...`.
fn write_supers<T: Write>(buffer: &mut T, count: usize) -> Result<(), fmt::Error> {
    for _ in 0..count {
        write!(buffer, "super::")?;
    }

    Ok(())
}

// Write the logic to convert a message from one type to another.
//
// Context variables:
// - `*writer` (in and out)
fn write_into_invocation<T: Write>(
    buffer: &mut T,
    type_variant: &schema::TypeVariant,
) -> Result<(), fmt::Error> {
    if let schema::TypeVariant::Array(inner_type) = type_variant {
        let mut layer = inner_type;
        while let schema::TypeVariant::Array(inner_type) = &layer.variant {
            layer = inner_type;
        }
        if let schema::TypeVariant::Custom(_, _) = &layer.variant {
            write!(buffer, ".into_iter().map(|x| ")?;
            layer = inner_type;
            while let schema::TypeVariant::Array(inner_type) = &layer.variant {
                layer = inner_type;
                write!(buffer, "x.into_iter().map(|x| ")?;
            }
            write!(buffer, "x.into()")?;
            layer = inner_type;
            while let schema::TypeVariant::Array(inner_type) = &layer.variant {
                layer = inner_type;
                write!(buffer, ").collect::<Vec<_>>()")?;
            }
            write!(buffer, ").collect::<Vec<_>>()")
        } else {
            write!(buffer, ".into()")
        }
    } else {
        write!(buffer, ".into()")
    }
}

// Write the logic to compute the encoded size of a value.
//
// Context variables:
// - `*payload` (in)
fn write_atlas_calculation<T: Write>(
    buffer: &mut T,
    supers: usize,
    type_variant: &schema::TypeVariant,
    is_field: bool,
) -> Result<(), fmt::Error> {
    write!(buffer, "(")?;

    match type_variant {
        schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
            schema::TypeVariant::Array(_)
            | schema::TypeVariant::Bytes
            | schema::TypeVariant::Custom(_, _)
            | schema::TypeVariant::String => {
                write!(buffer, "{{ let atlases = payload.iter().map(|payload| ")?;
                write_atlas_calculation(buffer, supers, &inner_type.variant, false)?;
                write!(
                    buffer,
                    ").collect::<Vec<_>>(); (atlases.iter().fold(0_usize, \
                    |x, payload_atlas| {{ let atlas_size = ",
                )?;
                write_atlas_lookup(buffer, &inner_type.variant)?;
                write!(buffer, "; x + ")?;
                write_supers(buffer, supers)?;
                write!(
                    buffer,
                    "varint_size_from_value(atlas_size as u64) + atlas_size }}), atlases) }}",
                )?;
            }
            schema::TypeVariant::Bool | schema::TypeVariant::S64 | schema::TypeVariant::U64 => {
                write!(buffer, "payload.iter().fold(0_usize, |x, payload| x + ")?;
                write_atlas_calculation(buffer, supers, &inner_type.variant, false)?;
                write!(buffer, ")")?;
            }
            schema::TypeVariant::F64 => {
                write!(buffer, "8_usize * payload.len()")?;
            }
            schema::TypeVariant::Unit => {
                write!(buffer, "{{ let payload = &(payload.len() as u64); ")?;
                write_atlas_calculation(buffer, supers, &schema::TypeVariant::U64, is_field)?;
                write!(buffer, " }}")?;
            }
        },
        schema::TypeVariant::Bool => {
            if is_field {
                write!(buffer, "usize::from(*payload)")?;
            } else {
                write!(buffer, "1_usize")?;
            }
        }
        schema::TypeVariant::Bytes | schema::TypeVariant::String => {
            write!(buffer, "payload.len()")?;
        }
        schema::TypeVariant::Custom(_, _) => {
            write!(buffer, "payload.atlas()")?;
        }
        schema::TypeVariant::F64 => {
            if is_field {
                write!(
                    buffer,
                    "if payload.to_bits() == 0_u64 {{ 0_usize }} else {{ 8_usize }}",
                )?;
            } else {
                write!(buffer, "8_usize")?;
            }
        }
        schema::TypeVariant::S64 => {
            write!(buffer, "{{ let zigzag = ")?;
            write_supers(buffer, supers)?;
            write!(buffer, "zigzag_encode(*payload); let payload = &zigzag; ")?;
            write_atlas_calculation(buffer, supers, &schema::TypeVariant::U64, is_field)?;
            write!(buffer, " }}")?;
        }
        schema::TypeVariant::U64 => {
            if is_field {
                write!(
                    buffer,
                    "match *payload {{ 0_u64 => {{ 0_usize }}, 1_u64..=567_382_630_219_903_u64 => \
                        {{ ",
                )?;
                write_supers(buffer, supers)?;
                write!(
                    buffer,
                    "varint_size_from_value(*payload) }}, 567_382_630_219_904_u64..=\
                        18_446_744_073_709_551_615_u64 => {{ 8_usize }} }}",
                )?;
            } else {
                write_supers(buffer, supers)?;
                write!(buffer, "varint_size_from_value(*payload)")?;
            }
        }
        schema::TypeVariant::Unit => {
            write!(buffer, "0_usize")?;
        }
    }

    write!(buffer, ")")
}

// Write the logic to look up the encoded size of a value from its atlas.
//
// Context variables:
// - `*payload_atlas` (in)
fn write_atlas_lookup<T: Write>(
    buffer: &mut T,
    type_variant: &schema::TypeVariant,
) -> Result<(), fmt::Error> {
    match type_variant {
        schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
            schema::TypeVariant::Array(_)
            | schema::TypeVariant::Bytes
            | schema::TypeVariant::Custom(_, _)
            | schema::TypeVariant::String => write!(buffer, "payload_atlas.0"),
            schema::TypeVariant::Bool
            | schema::TypeVariant::F64
            | schema::TypeVariant::S64
            | schema::TypeVariant::U64
            | schema::TypeVariant::Unit => write!(buffer, "*payload_atlas"),
        },
        schema::TypeVariant::Bool
        | schema::TypeVariant::Bytes
        | schema::TypeVariant::F64
        | schema::TypeVariant::S64
        | schema::TypeVariant::String
        | schema::TypeVariant::U64
        | schema::TypeVariant::Unit => write!(buffer, "*payload_atlas"),
        schema::TypeVariant::Custom(_, _) => write!(buffer, "payload_atlas.size()"),
    }
}

// Write the logic to invoke the serialization logic for a value, including a trailing line break.
//
// Context variables:
// - `*payload_atlas` (in)
// - `*payload` (in)
// - `*writer` (in and out)
//
// Additional notes:
// - If `is_field` is unset and `type_variant` is `Bool`, `S64`, `U64`, or `F64`, then
//   `payload_atlas` is never read.
#[allow(clippy::too_many_lines)]
fn write_serialization_invocation<T: Write>(
    buffer: &mut T,
    indentation: usize,
    supers: usize,
    type_variant: &schema::TypeVariant,
    is_field: bool,
) -> Result<(), fmt::Error> {
    match type_variant {
        schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
            schema::TypeVariant::Array(_)
            | schema::TypeVariant::Bytes
            | schema::TypeVariant::Custom(_, _)
            | schema::TypeVariant::String => {
                write_indentation(buffer, indentation)?;
                writeln!(
                    buffer,
                    "for (payload, payload_atlas) in \
                            payload.iter().zip(payload_atlas.1.iter()) {{",
                )?;
                write_indentation(buffer, indentation + 1)?;
                write_supers(buffer, supers)?;
                write!(buffer, "serialize_varint(")?;
                write_atlas_lookup(buffer, &inner_type.variant)?;
                writeln!(buffer, " as u64, writer)?;")?;
                write_serialization_invocation(
                    buffer,
                    indentation + 1,
                    supers,
                    &inner_type.variant,
                    false,
                )?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")
            }
            schema::TypeVariant::Bool
            | schema::TypeVariant::S64
            | schema::TypeVariant::U64
            | schema::TypeVariant::F64 => {
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "for payload in payload {{")?;
                write_serialization_invocation(
                    buffer,
                    indentation + 1,
                    supers,
                    &inner_type.variant,
                    false,
                )?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")
            }
            schema::TypeVariant::Unit => {
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "{{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "let varint = payload.len() as u64;")?;
                write_u64_serialization_invocation(buffer, indentation + 1, supers, is_field)?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")
            }
        },
        schema::TypeVariant::Bool => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "{{")?;
            write_indentation(buffer, indentation + 1)?;
            writeln!(buffer, "let varint = u64::from(*payload);")?;
            write_u64_serialization_invocation(buffer, indentation + 1, supers, is_field)?;
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "}}")
        }
        schema::TypeVariant::Bytes => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "writer.write_all(payload)?;")
        }
        schema::TypeVariant::Custom(_, _) => {
            write_indentation(buffer, indentation)?;
            writeln!(
                buffer,
                "payload.serialize_with_atlas(writer, payload_atlas)?;",
            )
        }
        schema::TypeVariant::F64 => {
            write_indentation(buffer, indentation)?;
            if is_field {
                writeln!(buffer, "if payload.to_bits() != 0_u64 {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "writer.write_all(&payload.to_le_bytes())?;")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")
            } else {
                writeln!(buffer, "writer.write_all(&payload.to_le_bytes())?;")
            }
        }
        schema::TypeVariant::S64 => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "{{")?;
            write_indentation(buffer, indentation + 1)?;
            write!(buffer, "let varint = ")?;
            write_supers(buffer, supers)?;
            writeln!(buffer, "zigzag_encode(*payload);")?;
            write_u64_serialization_invocation(buffer, indentation + 1, supers, is_field)?;
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "}}")
        }
        schema::TypeVariant::String => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "writer.write_all(payload.as_bytes())?;")
        }
        schema::TypeVariant::U64 => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "{{")?;
            write_indentation(buffer, indentation + 1)?;
            writeln!(buffer, "let varint = *payload;")?;
            write_u64_serialization_invocation(buffer, indentation + 1, supers, is_field)?;
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "}}")
        }
        schema::TypeVariant::Unit => Ok(()),
    }
}

// Write the logic to invoke the serialization logic for a varint, including a trailing line break.
//
// Context variables:
// - `varint` (in)
// - `*writer` (in and out)
fn write_u64_serialization_invocation<T: Write>(
    buffer: &mut T,
    indentation: usize,
    supers: usize,
    is_field: bool,
) -> Result<(), fmt::Error> {
    write_indentation(buffer, indentation)?;
    if is_field {
        writeln!(buffer, "match varint {{")?;
        write_indentation(buffer, indentation + 1)?;
        writeln!(buffer, "0_u64 => {{}}")?;
        write_indentation(buffer, indentation + 1)?;
        write!(buffer, "1_u64..=567_382_630_219_903_u64 => ")?;
        write_supers(buffer, supers)?;
        writeln!(buffer, "serialize_varint(varint, writer)?,")?;
        write_indentation(buffer, indentation + 1)?;
        writeln!(
            buffer,
            "567_382_630_219_904_u64..=18_446_744_073_709_551_615_u64 => \
                writer.write_all(&varint.to_le_bytes())?,",
        )?;
        write_indentation(buffer, indentation)?;
        writeln!(buffer, "}}")
    } else {
        write_supers(buffer, supers)?;
        writeln!(buffer, "serialize_varint(varint, writer)?;")
    }
}

// Write the logic to invoke the deserialization logic for a value, including a trailing line break.
//
// Context variables:
// - `payload_size` (in)
// - `payload` (out, introduced)
// - `sub_reader` (in and out)
//
// Additional notes:
// - If `type_variant` is `Array`, `Bytes`, `Custom`, or `String` and the encoded data is well-
//   formed, then `sub_reader` is consumed to the end.
// - If `type_variant` is `Array` and the element type is `Bool`, `S64`, or `U64`, then
//   `payload_size` is never read.
#[allow(clippy::too_many_lines)]
fn write_deserialization_invocation<T: Write>(
    buffer: &mut T,
    indentation: usize,
    supers: usize,
    imports: &BTreeMap<Identifier, schema::Namespace>,
    namespace: &schema::Namespace,
    type_variant: &schema::TypeVariant,
    is_field: bool,
) -> Result<(), fmt::Error> {
    match type_variant {
        schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
            schema::TypeVariant::Array(_)
            | schema::TypeVariant::Bytes
            | schema::TypeVariant::Custom(_, _)
            | schema::TypeVariant::String => {
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "let mut payload = Vec::new();")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "loop {{")?;
                write_indentation(buffer, indentation + 1)?;
                write!(buffer, "let payload_size = match ")?;
                write_supers(buffer, supers)?;
                writeln!(buffer, "deserialize_varint(&mut sub_reader) {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "Ok(payload_size) => payload_size as usize,")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "Err(err) => {{")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(
                    buffer,
                    "if let std::io::ErrorKind::UnexpectedEof = err.kind() {{",
                )?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "break;")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "return Err(err);")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}};")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(
                    buffer,
                    "let mut sub_reader = ::std::io::Read::take(\
                            &mut sub_reader, payload_size as u64);",
                )?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "payload.push({{")?;
                write_deserialization_invocation(
                    buffer,
                    indentation + 2,
                    supers,
                    imports,
                    namespace,
                    &inner_type.variant,
                    false,
                )?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "payload")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}});")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")
            }
            schema::TypeVariant::Bool
            | schema::TypeVariant::F64
            | schema::TypeVariant::S64
            | schema::TypeVariant::U64 => {
                write_indentation(buffer, indentation)?;
                write!(
                    buffer,
                    "fn deserialize_element<T: ::std::io::BufRead>(mut sub_reader: &mut T) -> \
                            ::std::io::Result<",
                )?;
                write_type(buffer, imports, namespace, &inner_type.variant, In)?;
                writeln!(buffer, "> {{")?;
                write_deserialization_invocation(
                    buffer,
                    indentation + 1,
                    supers,
                    imports,
                    namespace,
                    &inner_type.variant,
                    false,
                )?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "Ok(payload)")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "let mut payload = Vec::new();")?;
                if matches!(inner_type.variant, schema::TypeVariant::F64) {
                    write_indentation(buffer, indentation)?;
                    writeln!(buffer, "payload.reserve_exact(payload_size);")?;
                }
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "loop {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(
                    buffer,
                    "payload.push(match deserialize_element(&mut sub_reader) {{",
                )?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "Ok(element) => element,")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "Err(err) => {{")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(
                    buffer,
                    "if let std::io::ErrorKind::UnexpectedEof = err.kind() {{",
                )?;
                write_indentation(buffer, indentation + 4)?;
                writeln!(buffer, "break;")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 3)?;
                writeln!(buffer, "return Err(err);")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}});")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}}")
            }
            schema::TypeVariant::Unit => {
                write_deserialization_invocation(
                    buffer,
                    indentation,
                    supers,
                    imports,
                    namespace,
                    &schema::TypeVariant::U64,
                    is_field,
                )?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "let payload = vec![(); payload as usize];")
            }
        },
        schema::TypeVariant::Bool => {
            write_deserialization_invocation(
                buffer,
                indentation,
                supers,
                imports,
                namespace,
                &schema::TypeVariant::U64,
                is_field,
            )?;
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "let payload = payload != 0_u64;")
        }
        schema::TypeVariant::Bytes => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "let mut payload = vec![];")?;
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "payload.reserve_exact(payload_size);")?;
            write_indentation(buffer, indentation)?;
            writeln!(
                buffer,
                "::std::io::Read::read_to_end(&mut sub_reader, &mut payload)?;",
            )
        }
        schema::TypeVariant::Custom(_, _) => {
            write_indentation(buffer, indentation)?;
            write!(buffer, "let payload = ")?;
            write_type(buffer, imports, namespace, type_variant, In)?;
            writeln!(buffer, "::deserialize_from_reader_ref(&mut sub_reader)?;")
        }
        schema::TypeVariant::F64 => {
            write_indentation(buffer, indentation)?;
            if is_field {
                writeln!(buffer, "let payload = if payload_size == 0_usize {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "0.0_f64")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}} else {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "let mut buffer = [0; 8];")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(
                    buffer,
                    "::std::io::Read::read_exact(&mut sub_reader, &mut buffer)?;",
                )?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "f64::from_le_bytes(buffer)")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}};")
            } else {
                writeln!(buffer, "let mut buffer = [0; 8];")?;
                write_indentation(buffer, indentation)?;
                writeln!(
                    buffer,
                    "::std::io::Read::read_exact(&mut sub_reader, &mut buffer)?;",
                )?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "let payload = f64::from_le_bytes(buffer);")
            }
        }
        schema::TypeVariant::S64 => {
            write_deserialization_invocation(
                buffer,
                indentation,
                supers,
                imports,
                namespace,
                &schema::TypeVariant::U64,
                is_field,
            )?;
            write_indentation(buffer, indentation)?;
            write!(buffer, "let payload = ")?;
            write_supers(buffer, supers)?;
            writeln!(buffer, "zigzag_decode(payload);")
        }
        schema::TypeVariant::String => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "let mut payload = String::new();")?;
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "payload.reserve_exact(payload_size);")?;
            write_indentation(buffer, indentation)?;
            writeln!(
                buffer,
                "::std::io::Read::read_to_string(&mut sub_reader, &mut payload)?;",
            )
        }
        schema::TypeVariant::U64 => {
            write_indentation(buffer, indentation)?;
            if is_field {
                writeln!(buffer, "let payload = match payload_size {{")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "0_usize => 0_u64,")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "8_usize => {{")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "let mut buffer = [0; 8];")?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(
                    buffer,
                    "::std::io::Read::read_exact(&mut sub_reader, &mut buffer[..])?;",
                )?;
                write_indentation(buffer, indentation + 2)?;
                writeln!(buffer, "u64::from_le_bytes(buffer)")?;
                write_indentation(buffer, indentation + 1)?;
                writeln!(buffer, "}}")?;
                write_indentation(buffer, indentation + 1)?;
                write!(buffer, "_ => ")?;
                write_supers(buffer, supers)?;
                writeln!(buffer, "deserialize_varint(&mut sub_reader)?,")?;
                write_indentation(buffer, indentation)?;
                writeln!(buffer, "}};")
            } else {
                write!(buffer, "let payload = ")?;
                write_supers(buffer, supers)?;
                writeln!(buffer, "deserialize_varint(&mut sub_reader)?;")
            }
        }
        schema::TypeVariant::Unit => {
            write_indentation(buffer, indentation)?;
            writeln!(buffer, "let payload = ();")
        }
    }
}

// Determine whether a type is encoded as a varint.
fn integer_encoded(r#type: &schema::Type) -> bool {
    match &r#type.variant {
        schema::TypeVariant::Bool | schema::TypeVariant::S64 | schema::TypeVariant::U64 => true,
        schema::TypeVariant::Array(_)
        | schema::TypeVariant::Bytes
        | schema::TypeVariant::Custom(_, _)
        | schema::TypeVariant::F64
        | schema::TypeVariant::String
        | schema::TypeVariant::Unit => false,
    }
}

#[cfg(test)]
mod tests {
    use {
        crate::{generate_rust::generate, schema_loader::load_schemas, validator::validate},
        std::{fs::read_to_string, path::Path},
    };

    #[test]
    fn generate_example() {
        let schemas = load_schemas(Path::new("integration_tests/types/types.t")).unwrap();
        validate(&schemas).unwrap();

        assert_eq!(
            generate("0.0.0", &schemas),
            read_to_string("test_data/types.rs").unwrap(),
        );
    }
}
